use std::sync::Arc;

use crate::websockt::WebSocketFrame;

pub use super::pool::WebSocketConnectionPool;
use tokio::sync::RwLock;
use uuid::Uuid;

#[derive(Clone)]
pub struct WebSocketSession {
    pool: Arc<RwLock<WebSocketConnectionPool>>,
    uuid: Uuid,
}

impl WebSocketSession {
    pub fn new(uuid: Uuid, pool: Arc<RwLock<WebSocketConnectionPool>>) -> Self {
        Self { pool, uuid }
    }
}

impl WebSocketSession {
    /// Send frame to client which triggle this handler
    pub async fn send_frame(&self, frame: WebSocketFrame) {
        let pool = self.pool.read().await;
        if let Some(tx) = pool.peers.get(&self.uuid) {
            let _ = tx.send(frame);
        }
    }

    /// Broadcast frame to all client in pool
    pub async fn broadcast_frame(&self, frame: &WebSocketFrame) {
        let pool = self.pool.read().await;
        for (_, tx) in pool.peers.iter() {
            let _ = tx.send(frame.clone());
        }
    }    
    
    /// Broadcast frame to all client in pool except current
    pub async fn broadcast_frame_except_current(&self, frame: &WebSocketFrame) {
        self.broadcast_frame_with(frame, |id| *id != self.uuid).await;
    }

    /// Broadcast frame to client with meet `predicate`
    pub async fn broadcast_frame_with(&self, frame: &WebSocketFrame, predicate: impl Fn(&Uuid) -> bool) {
        let pool = self.pool.read().await;
        for (_, tx) in pool.peers.iter().filter(|(id, _)| predicate(*id)) {
            let _ = tx.send(frame.clone());
        }
    }
}

impl WebSocketSession {
    /// Send message to client which triggle this handler
    pub async fn send_message<S: Into<String>>(&self, message: S) {
        self.send_frame(WebSocketFrame::server_text(message)).await;
    }

    /// Broadcast message to all client in pool
    pub async fn broadcast_message<S: Into<String>>(&self, message: S) {
        self.broadcast_frame(&WebSocketFrame::server_text(message)).await;
    }    
    
    /// Broadcast message to all client in pool except current
    pub async fn broadcast_message_except_current<S: Into<String>>(&self, message: S) {
        self.broadcast_frame_except_current(&WebSocketFrame::server_text(message)).await;
    }

    /// Broadcast message to client with meet `predicate`
    pub async fn broadcast_message_with<S: Into<String>>(&self, message: S, predicate: impl Fn(&Uuid) -> bool) {
        self.broadcast_frame_with(&WebSocketFrame::server_text(message), predicate).await;
    }
}